package edu.gatech.cs3240;

import java.util.ArrayList;

import edu.gatech.cs3240.NFA.NFA.InvalidNFATraversalException;

/**
 * A threaded parsing mechanism for determining whether or not the incoming
 * string matches the provided regex.
 * @author VRamachandran
 */
public class NewParser extends Thread {
	
	private edu.gatech.cs3240.NFA.State state;
	private String workingString;
	private NewParser parent;
	private ArrayList<NewParser> children;
	private Status myStatus;
	// private int id;
	// Assures that each newly generated parser is given a unique id (for 
	// debugging purposes).
	public static int nextId;
	
	public enum Status{
		running,
		succeeded,
		failed,
		killed,
		waiting
	}
	
	public NewParser (int id, NewParser parent, edu.gatech.cs3240.NFA.State s, String workingString) {
		this.state = s;
		this.workingString = workingString;
		this.parent = parent;
		this.myStatus = Status.running;
		//this.id = id;
		this.children = new ArrayList<NewParser>();
	}
	
	@Override
	public void run() {
		// System.err.println(id + ": Starting RUN.");
		NewStartParser.visit(state, workingString);
		// Do this periodically!
		synchronized (myStatus) {
			if(getStatus() == Status.killed) {
				// System.err.println(id + ": Killed.");
				return;
			}
		}
		
		if(workingString.length() == 0) {
			// If the string is empty, I need to see if I can accept right 
			// now.
			if(state.isAccepting()) {
				// System.err.println(id + ": ACCEPT string.");
				parent.reportSuccess(this);
				killChildren();
				return;
			}
		}

		synchronized (myStatus) {
			if(getStatus() == Status.killed) {
				// System.err.println(id + ": Killed.");
				return;
			}
		}
		
		// Do I have any epsilon transitions?  If so, send children in those
		// directions.
		synchronized(children) {
			for(int i = 0; i < state.getPaths().size(); i++) {
				if(state.getPaths().get(i).isEpsilon()) {
					if(NewStartParser.hasBeenVisited(state.getPaths().get(i).getTarget(), workingString)) {
						// System.err.println(id + ": Got an epsilon transition, but I've already been there.");
						continue;
					}
					// System.err.println(id + ": Got an epsilon transition, spawning.");
					NewParser np = new NewParser(nextId++, this, 
							state.getPaths().get(i).getTarget(), 
							workingString.substring(0));
					children.add(np);
					np.start();
				}
			}
		}

		synchronized (myStatus) {
			if(getStatus() == Status.killed) {
				// System.err.println(id + ": Killed.");
				return;
			}
		}
		
		// Now I'll see if I can start processing the string myself.
		for(int i = 0; i < state.getPaths().size(); i++) {
			if((!state.getPaths().get(i).isEpsilon()) && 
					(workingString.startsWith(state.getPaths().get(i)
							.getConsumable().toString()))) {
				// System.err.println(id + ": Consuming a '" + state.getPaths().get(i)
				//		.getConsumable().toString() + "'.");
				try {
					this.workingString = state.getPaths().get(i).consume(workingString);
				} catch (InvalidNFATraversalException yfue) {
					yfue.printStackTrace();
				}
				this.state = state.getPaths().get(i).getTarget();
				run();
				// System.err.println(id + ": Finished a nested run!");
			}
		}

		synchronized (myStatus) {
			if(getStatus() == Status.killed) {
				// System.err.println(id + ": Killed.");
				return;
			}
			// Now that I've failed to process the string myself, it's up to my
			// children, if there are any.
			if(allChildrenFailed()) {
				setStatus(Status.failed);
				parent.reportFailure(this);
				// System.err.println(id + ": Done, but didn't make any kids, so I fail.");
			}
			else {
				setStatus(Status.waiting);
				// System.err.println(id + ": Gonna wait for my kids.");
			}
		}
	}
	
	/**
	 * Allows a child thread of this parser thread to report that it has 
	 * successfully consumed the end of the input string at an accepting state.
	 * @param p The child thread.
	 */
	public synchronized void reportSuccess(NewParser p) {
		setStatus(Status.killed);
		parent.reportSuccess(this);
	}
	
	/**
	 * Allows a child thread of this parser thread to report that it (and all 
	 * of its children) have been exhausted all options and have been unable to
	 * match the input string at an accepting state.
	 * @param p The child thread.
	 */
	public synchronized void reportFailure(NewParser p) {
		if(allChildrenFailed()) {
			// System.err.println(id + ": All my kids failed.  Reporting.");
			parent.reportFailure(this);
		}
	}
	
	/**
	 * Runs through this thread's children, to determine if all of them have
	 * failed.
	 * @return false if any single child is still running, otherwise true.
	 */
	private boolean allChildrenFailed() {
		for(int i = 0; i < children.size(); i++) {
			if(children.get(i).getStatus() == Status.running)
				return false;
		}
		return true;
	}
	
	public Status getStatus() {
		return myStatus;
	}
	
	/**
	 * Set's this thread's status.  If you instruct the thread to be 'killed',
	 * it will also kill all of its child threads.
	 * @param s the status to set.
	 */
	public synchronized void setStatus(Status s) {
		if(myStatus == Status.killed)
			// I'm already dead, I'm not doing anything else.
			return;
		myStatus = s;
		if(s == Status.killed)
			killChildren();
	}
	
	/**
	 * Kills all of the children threads of this thread.
	 */
	private synchronized void killChildren() {
		synchronized(children) {
			for(int i = 0; i < children.size(); i++) {
				if((children.get(i).getStatus() == Status.running) ||
						(children.get(i).getStatus() == Status.waiting))
					children.get(i).setStatus(Status.killed);
			}
		}
	}

}
